package com.flute.icrawler.policy.updatepolicy.adjustment;

import java.io.ByteArrayInputStream;
import java.util.Calendar;
import java.util.List;
import java.util.Set;

import org.apache.log4j.Logger;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import com.flute.icrawler.htmlparser.impl.BasicHtmlParser;
import com.flute.icrawler.htmlparser.util.DOMUtil;
import com.flute.haflute.monitor.GeneralStatusCenter;
import com.flute.haflute.monitor.StatusMessage;
import com.flute.icrawler.app.service.CrawlService;
import com.flute.icrawler.config.AdjustPolicy;
import com.flute.icrawler.policy.bdb.storage.BdbStorageUtil;
import com.flute.icrawler.policy.bdb.storage.IStorage;
import com.flute.icrawler.policy.updatepolicy.bean.PolicyURL;
import com.flute.icrawler.policy.updatepolicy.framework.UpdatePolicyController;
import com.flute.icrawler.policy.updatepolicy.util.HtmlAnalyzer;
import com.sleepycat.je.Transaction;

/**
 * 更新粒度调整类
 * 
 */
public class PolicyAdjuster implements java.lang.Runnable {
	private static Logger logger = Logger.getLogger(PolicyAdjuster.class);
	private static java.text.SimpleDateFormat format = new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

	private static UpdatePolicyController controller;

	/**
	 * 
	 * @param controller
	 */
	public PolicyAdjuster(UpdatePolicyController controller) {
		PolicyAdjuster.controller = controller;
	}

	@Override
	public void run() {
		while (true) {
			try {
				final PolicyURL url = controller.pollUrl();
				controller.executeAdjuester(new Runnable() {
					public void run() {
						adjust(url);
					}
				});
			} catch (Exception e) {
				logger.error("更新模块对url进行后续处理异常", e);
			}
		}
	}

	/**
	 * 更新粒度粒度调整
	 * 
	 * @param url
	 */
	private void adjust(final PolicyURL url) {
		// Logger.getLogger(PolicyAdjuster.class).setLevel(Level.OFF);
		logger.info("更新模块开始处理url=" + url.getUrl() + "depth=" + url.getUrlDepth());

		url.setUpdateTime(System.currentTimeMillis());
		url.setSegmentation(HtmlAnalyzer.getSegmentation(url.getContent()));

		Integer currentG = controller.getGranularity(url.getUrl());
		// 新的URL没有粒度
		if (currentG == null) {
			this.initGranularity(url);
			return;
		} else if (currentG == 0) { // 粒度为0的url不做更新操作,也不需要调整粒度
			return;
		}
		url.setChangStatus(this.getChangStatus(url, currentG));

		IStorage storage = controller.getStorage(currentG);
		// 种子页不做调整，只是修改更新时间
		if (url.getUrlDepth() == 1) {
			storage.put(url.getUrl(), url);
			return;
		}

		// 内容发生变化
		if (this.isChanged(url, currentG)) {
			controller.IncrUpdateCount();
			this.reportMessage(url.getUrl(), currentG);
			url.setChangStatus(url.getChangStatus() + 1);
			Integer nextG = controller.getPreGranularity(currentG);
			if (nextG != null && this.isAdjust(currentG, url.getChangStatus())) {
				this.changeGranularity(url, currentG, nextG);
				String time = format.format(java.util.Calendar.getInstance().getTime());
				CrawlService.getInstance().logResult("update",
						time + "=内容变化调整粒度[currG=" + currentG + "|nextG=" + nextG + "]url=" + url.getUrl());
				logger.info("内容变化调整粒度[currG=" + currentG + "|nextG=" + nextG + "]url=" + url.getUrl());
			} else {
				storage.put(url.getUrl(), url);
				CrawlService.getInstance().logResult("update", "内容变化,但已是最小更新粒度，不再调整");
				logger.info("内容发生变化,但已是最小更新粒度，不再调整");
			}
		} else {
			url.setChangStatus(url.getChangStatus() - 1);
			Integer nextG = controller.getNextGranularity(currentG);
			if (nextG != null && this.isAdjust(currentG, url.getChangStatus())) {
				this.changeGranularity(url, currentG, nextG);
				String time = format.format(java.util.Calendar.getInstance().getTime());
				CrawlService.getInstance().logResult("update",
						time + "=内容没有变化调整粒度[currG=" + currentG + "|nextG=" + nextG + "]url=" + url.getUrl());
				logger.info("内容没有变化调整粒度[currG=" + currentG + "|nextG=" + nextG + "]url=" + url.getUrl());
			} else {
				storage.put(url.getUrl(), url);
				CrawlService.getInstance().logResult("update", "内容没有变化,但已是最大更新粒度，不再调整");
				logger.info("内容没有变化,但已是最大更新粒度，不再调整url=" + url.getUrl());
			}
		}
	}

	/**
	 * 改变粒度/存储库
	 * 
	 * @param url
	 * @param currentPolicy
	 * @param nextPolicy
	 */
	private void changeGranularity(PolicyURL url, int currentPolicy, int nextPolicy) {
		url.setChangStatus(0);
		IStorage currentStroage = controller.getStorage(currentPolicy);
		IStorage nextStroage = controller.getStorage(nextPolicy);
		// 开始事务
		Transaction txn = BdbStorageUtil.beginTransaction();
		currentStroage.delete(url.getUrl());
		nextStroage.put(url.getUrl(), url);
		// 事务提交
		txn.commit();
	}

	/**
	 * 
	 * @param granularity
	 * @return
	 */
	private boolean isAdjust(int granularity, int changStatus) {
		List<AdjustPolicy> policys = controller.getPolicy(granularity).getAdjustpolicys();
		for (AdjustPolicy policy : policys) {
			String time = policy.getTime();
			if (time == null || !this.hasBetweenTime(time)) {
				continue;
			}
			int interval = policy.getInterval();
			if (Math.abs(changStatus) >= interval) {
				return true;
			} else {
				return false;
			}
		}
		return false;
	}

	/**
	 * 判断当前时间点是否在policyTime时间段内
	 * 
	 * @param policyTime
	 * @return
	 */
	private boolean hasBetweenTime(String policyTime) {
		try {
			java.util.Calendar c = java.util.Calendar.getInstance();
			java.util.Date nowTime = c.getTime();

			String startTime = policyTime.substring(0, policyTime.indexOf('-'));
			String startHour = startTime.substring(0, startTime.indexOf(':'));
			String startMin = startTime.substring(startTime.indexOf(':') + 1);
			c.set(Calendar.HOUR_OF_DAY, Integer.parseInt(startHour));
			c.set(Calendar.MINUTE, Integer.parseInt(startMin));
			c.set(Calendar.SECOND, Integer.valueOf(0));
			java.util.Date start = c.getTime();

			String endTime = policyTime.substring(policyTime.indexOf('-') + 1);
			String endHour = endTime.substring(0, endTime.indexOf(':'));
			String endMin = endTime.substring(endTime.indexOf(':') + 1);
			c.set(Calendar.HOUR_OF_DAY, Integer.parseInt(endHour));
			c.set(Calendar.MINUTE, Integer.parseInt(endMin));
			c.set(Calendar.SECOND, Integer.valueOf(59));
			java.util.Date end = c.getTime();
			if (start.before(nowTime) && end.after(nowTime)) {
				return true;
			}
		} catch (Exception e) {
			logger.error(e);
		}
		return false;
	}

	/**
	 * 内容是否发生变化
	 * 
	 * @param url
	 * @param granu
	 * @return
	 */
	private boolean isChanged(PolicyURL url, Integer currentG) {
		try {
			if (this.isNews(url)) {
				return false;
			}
			PolicyURL PreUrl = (PolicyURL) controller.getStorage(currentG).search(url.getUrl());
			Set<String> preSegment = PreUrl.getSegmentation();
			Set<String> curSegment = url.getSegmentation();
			double count = 0.0;
			for (String segment : curSegment) {
				if (preSegment.contains(segment)) {
					count++;
				}
			}
			if (count == curSegment.size()) {
				return true;
			}
		} catch (Exception e) {
			logger.error(e);
		}
		return false;
	}

	/**
	 * 
	 * @param url
	 * @param currentG
	 * @return
	 */
	private int getChangStatus(PolicyURL url, Integer currentG) {
		try {
			PolicyURL PreUrl = (PolicyURL) controller.getStorage(currentG).search(url.getUrl());
			return PreUrl.getChangStatus();
		} catch (Exception e) {
			logger.error(e);
		}
		return 0;
	}

	/**
	 * 是否为新闻页面
	 * 
	 * @param url
	 * @return
	 */
	private boolean isNews(PolicyURL url) {
		try {
			if (url.getContent() == null || url.getContent().trim().equals("")) {
				logger.info("url中的内容为空url=" + url.getUrl());
				return false;
			}
			Document doc = new BasicHtmlParser().getDocument(new ByteArrayInputStream(url.getContent()
					.getBytes("utf-8")));
			NodeList nodes = DOMUtil.xpath(doc, "//A");
			int aSize = nodes.getLength();
			// 链接数大于500不是新闻页面
			if (aSize > 500) {
				return false;
			}
			double aLength = 0.0;
			for (int i = 0; i < aSize; i++) {
				Node node = nodes.item(i);
				aLength += DOMUtil.getNodeValue(node).length();
			}
			double contentLength = DOMUtil.getNodeValue(doc.getDocumentElement()).length();
			double i = aLength / contentLength;
			// 链接文字占页面总文字的45%就不是新闻页面
			if (i >= 0.45) {
				return false;
			} else {
				return true;
			}
		} catch (Exception e) {
			logger.error("isNews:url=" + url.getUrl(), e);
		}
		return false;
	}

	/**
	 * 初始更新粒度
	 * 
	 * @param url
	 */
	private void initGranularity(PolicyURL url) {
		if (url.getUrlDepth() < 1) {
			return;
		}
		try {
			int pathDepth = this.getPathDepth(url.getUrl());
			Integer granu = null;
			if (url.getUrlDepth() != 1 && pathDepth >= 5) {
				granu = controller.getGranularity(controller.getGranularitysSize() - 1);
			} else if (url.getUrlDepth() != 1 && this.isNews(url)) {
				granu = controller.getGranularity(controller.getGranularitysSize() - 1);
			} else if (url.getUrlDepth() == 1) {
				granu = controller.getGranularity(0);
			} else if (url.getUrlDepth() == 2 && pathDepth == 1) {
				granu = controller.getGranularity(1);
			} else if (url.getUrlDepth() == 2 && pathDepth == 2) {
				granu = controller.getGranularity(2);
			} else if (url.getUrlDepth() == 2 && pathDepth == 3) {
				granu = controller.getGranularity(3);
			} else if (url.getUrlDepth() == 2 && pathDepth > 3) {
				granu = controller.getGranularity(4);
			} else if (url.getUrlDepth() == 3) {
				granu = controller.getGranularity(controller.getGranularitysSize() - 3);
			} else if (url.getUrlDepth() == 4) {
				granu = controller.getGranularity(controller.getGranularitysSize() - 2);
			} else {
				granu = controller.getGranularity(controller.getGranularitysSize() - 1);
			}
			controller.getStorage(granu).put(url.getUrl(), url);
			if (granu != 0) {
				CrawlService.getInstance().logResult("update", "收到新的URL,初始更新粒度G=[" + granu + "]url=" + url.getUrl());
			}
			logger.info("收到新的URL,初始更新粒度G=[" + granu + "]url=" + url.getUrl());
		} catch (Exception e) {
			logger.error("为url设置更新粒度异常" + e);
		}
	}

	/**
	 * 获取url深度
	 * 
	 * @param url
	 * @return
	 */
	private int getPathDepth(String url) {
		return url.replaceFirst("^.+://", "").split("\\/").length;
	}

	/**
	 * 上报消息
	 * 
	 * @param url
	 * @param granu
	 */
	private void reportMessage(String url, Integer granu) {
		try {
			String time = format.format(java.util.Calendar.getInstance().getTime());
			// 上报消息
			StatusMessage statusMessage = new StatusMessage();
			statusMessage.topic = "更新模块";
			statusMessage.content = "对URL[" + url + "]完成一次更新,更新频率=" + granu + ",当前更新总数=" + controller.getUpdateConut();
			GeneralStatusCenter.getInstance().report(statusMessage);
			CrawlService.getInstance().logResult("update",
					time + "=对URL[" + url + "]完成一次更新,更新频率=" + granu + ",当前更新总数=" + controller.getUpdateConut());
			logger.info("对URL[" + url + "]完成一次更新");
		} catch (Exception e) {
			logger.error(e);
		}
	}

	public static void main(String[] args) {
		System.out.print(new PolicyAdjuster(null).hasBetweenTime("16:17-16:20"));
	}

}
